import { z } from "zod";
import "reflect-metadata";
import { applyDecorators } from "@nestjs/common";
import { ApiExtraModels, ApiProperty, ApiPropertyOptions, ApiResponse, ApiResponseOptions, getSchemaPath } from "@nestjs/swagger";

/**
 * 元数据designType转zodType类型
 */
export const designType2zodType = {
  'undefined': z.undefined(),
  'Number': z.number(),
  'String': z.string(),
  'Boolean': z.boolean(),
  'Array': z.array(z.any()),
  'Date': z.date(),
  'Function': z.function(),
  'Object': z.any(),
}

/**
 * 代替ApiProperty
 */
export function ApiProp(props: {
  desc: string
  zodType?: z.ZodTypeAny
} & Partial<Omit<ApiPropertyOptions, 'type'|'description'>>) {
  const decorator: PropertyDecorator = (target, propertyKey) => {
    const { desc, zodType: _zodType, ...otherProps } = props;
    const designType = Reflect.getMetadata('design:type', target, propertyKey).name;
    /** 不传则从ts元数据里面推断 */
    const zodType = _zodType || designType2zodType[designType] || z.any();
    /** 给属性赋值为zod对象 */
    target[propertyKey] = zodType;
    /** 内部调用ApiProperty */
    ApiProperty({
      ...otherProps,
      description: desc,
    })(target, propertyKey)
  }
  return decorator;
}

/** 
 * 接口响应成功格式
 */
export class ApiSuccessResponseDto<T> {
  @ApiProperty({ description: '用于判断是否成功' })
  success!: boolean;
  
  /** 响应体 */
  data!: T
}

/**
 * 接口响应失败格式
 */
export class ApiErrorResponseDto<T> {
  @ApiProperty({ description: '用于判断是否成功' })
  success!: boolean;

  @ApiProperty({ description: '状态码, 不同状态码代表不同含义' })
  code!: string;
  
  @ApiProperty({ description: '错误信息' })
  msg!: string;

  @ApiProperty({ description: '弹窗时间' })
  msgTimeout?: string;
}

/**
 * 操作成功, 空对象
 */
export class EmptyData {
  
}

/** 
 * 分页对象基本格式
 */
export class Pagination {
  @ApiProperty({ description: '当前页码' })
  currentPage!: number;

  @ApiProperty({ description: '每页数量' })
  pageSize!: number;
  
  @ApiProperty({ description: '总数', required: false })
  totalCount!: number;
}

/** 
 * JSON响应
 * @example @ApiJsonResponse(UserVo)
 */
export function ApiJsonResponse<T extends Function>(
  model: T,
  options?: Omit<ApiResponseOptions, 'description'> & { desc?: string },
) {
  return applyDecorators(
    /** 注入模型 */
    ApiExtraModels(model),
    ApiResponse({
      ...options,
      schema: {
        allOf: [
          { $ref: getSchemaPath(ApiSuccessResponseDto) },
          {
            properties: {
              data: {
                $ref: getSchemaPath(model),
              },
            },
          },
        ],
      },
    }),
  );
};

/** 
 * 数组响应 
 * @example @ApiListResponse(UserVo)
 */
export function ApiListResponse<T extends Function>(
  model: T,
  options?: Omit<ApiResponseOptions, 'description'> & { desc?: string },
) {
  return applyDecorators(
    /** 注入模型 */
    ApiExtraModels(model),
    ApiResponse({
      ...options,
      schema: {
        allOf: [
          { $ref: getSchemaPath(ApiSuccessResponseDto) },
          {
            properties: {
              data: {
                type: 'array',
                items: {
                  $ref: getSchemaPath(model),
                }
              },
            },
          },
        ],
      },
    }),
  );
};

/** 
 * 分页数据响应 
 * @example @ApiPaginationResponse(UserVo)
 */
export function ApiPaginationResponse<T extends Function>(
  model: T,
  options?: Omit<ApiResponseOptions, 'description'> & { desc?: string },
) {
  return applyDecorators(
    /** 注入模型 */
    ApiExtraModels(model),
    ApiResponse({
      ...options,
      schema: {
        allOf: [
          { $ref: getSchemaPath(ApiSuccessResponseDto) },
          {
            properties: {
              data: {
                allOf: [
                  {
                    $ref: getSchemaPath(Pagination)
                  },
                  {
                    properties: {
                      list: {
                        type: 'array',
                        items: {
                          $ref: getSchemaPath(model),
                        }
                      },
                    },
                  }
                ]
              }
            }
          },
        ],
      },
    }),
  );
};
